package utility;

import java.util.*;

public class HashBucket implements Iterable<Map.Entry<String, HashBucket>> {

	//private vars
	private Map<String, HashBucket> childBuckets 			= null;
	private Integer indexEntry								= null;
	private Map<String, Set<Integer>> foreignIndexEntries	= null;
	
	public final Integer depth;
	
	//constructors
	public HashBucket(Integer depth) {
		if(depth <= 0)
			throw new IllegalArgumentException("Depth must be 1 or higher");
		this.depth = depth;
		childBuckets 		= new HashMap<String, HashBucket>();
	}
	private HashBucket() { 
		depth = 0;
		foreignIndexEntries	= new HashMap<String, Set<Integer>>();
	}
	
	//util methods
	public int size() {
		if(depth == 1) {
			return childBuckets.size();
		} else {
			int size = 0;
			for(String dataValue : childBuckets.keySet()) {
				size += childBuckets.get(dataValue).size();
			}
			return size;
		}
	}
	
	private String getCorrespondingDataValue(List<String> dataValues) { 
		return dataValues.get(dataValues.size()-depth); 
	}
	
	private HashBucket addChildBucket(String dataValue) {
		HashBucket childBucket;
		if(depth == 1) {
			childBucket = new HashBucket();
		} else {
			childBucket = new HashBucket(depth-1);
		}
		
		childBuckets.put(dataValue, childBucket);
		
		return childBucket;
	}
	
	public void printStructure() {	printStructureRecursive(0); }
	private void printStructureRecursive(int currentDepth) {
		String tab = "\t";
		for(int i = 0; i < currentDepth; ++i)
			tab = tab+"\t";
		
		for(String value : childBuckets.keySet()) {
			HashBucket childBucket = childBuckets.get(value);
			if(depth == 1) {
				System.out.println(tab+value+"["+childBucket.indexEntry+"] :");
				for(String foreignKeyName : childBucket.foreignIndexEntries.keySet()) {
					System.out.println(tab+"\t"+foreignKeyName+": "+Arrays.toString(childBucket.foreignIndexEntries.get(foreignKeyName).toArray()));
				}
			} else {
				System.out.println(tab+value+" {");
				childBucket.printStructureRecursive(currentDepth+1);
				System.out.println(tab+"}");
			}
		}
	}
	
	//interface implementations
	public Iterator<Map.Entry<String, HashBucket>> iterator() {
		return new HashSet<Map.Entry<String, HashBucket>>(childBuckets.entrySet()).iterator();
	}
	
	//complex getters
	public Integer getIndexEntry(List<String> dataValues) {
		String dataValue = getCorrespondingDataValue(dataValues);
		HashBucket childBucket = childBuckets.get(dataValue);
		if(childBucket == null) 
			return null;
		
		if(depth == 1) {
			return childBucket.indexEntry;
		} else {
			return childBucket.getIndexEntry(dataValues);
		}
	}
	
	public Set<Integer> getForeignIndexEntries(List<String> dataValues, String foreignIndexName) {
		String dataValue = getCorrespondingDataValue(dataValues);
		HashBucket childBucket = childBuckets.get(dataValue);
		
		if(childBucket == null) 
			return null;
		
		if(depth == 1) {
			Set<Integer> foreignIndexEntries = childBucket.foreignIndexEntries.get(foreignIndexName);
			
			if(foreignIndexEntries == null)
				foreignIndexEntries = new HashSet<Integer>();
			else
				foreignIndexEntries = new HashSet<Integer>(foreignIndexEntries);
				
			return foreignIndexEntries;
		} else {
			return childBucket.getForeignIndexEntries(dataValues, foreignIndexName);
		}
	}
	
	public Set<String> getForeignIndexes(List<String> dataValues) {
		String dataValue = getCorrespondingDataValue(dataValues);
		HashBucket childBucket = childBuckets.get(dataValue);
		
		if(childBucket == null) 
			return new HashSet<String>();
		
		if(depth == 1) {
			return new HashSet<String>(childBucket.foreignIndexEntries.keySet());
		} else {
			return childBucket.getForeignIndexes(dataValues);
		}
	}
	
	public Set<Integer> getAllIndexEntries() {
		Set<Integer> entries = new HashSet<Integer>();
		if(depth == 0) {
			entries.add(indexEntry);
		} else if(depth == 1) {
			for(Map.Entry<String, HashBucket> entry : childBuckets.entrySet()) {
				entries.add(entry.getValue().indexEntry);
			}
		} else {
			for(Map.Entry<String, HashBucket> entry : childBuckets.entrySet()) {
				entries.addAll(entry.getValue().getAllIndexEntries());
			}
		}
		return entries;
	}
	
	//complex setters
	public void setIndexEntry(List<String> dataValues, Integer newIndexEntry) {
		String dataValue = getCorrespondingDataValue(dataValues);
		HashBucket childBucket = childBuckets.get(dataValue);
		
		if(childBucket == null) {
			childBucket = addChildBucket(dataValue);
			childBuckets.put(dataValue, childBucket);
		}
		
		if(depth == 1) {
			childBucket.indexEntry = newIndexEntry;
		} else {
			childBucket.setIndexEntry(dataValues, newIndexEntry);
		}
	}
	
	public void addForeignIndexEntry(List<String> dataValues, String foreignIndexName, Integer newForeignIndexEntry) {
		String dataValue = getCorrespondingDataValue(dataValues);
		HashBucket childBucket = childBuckets.get(dataValue);
		
		if(depth == 1) {
			Set<Integer> existingForeignIndexEntries = childBucket.foreignIndexEntries.get(foreignIndexName);
			
			if(existingForeignIndexEntries == null) {
				existingForeignIndexEntries = new HashSet<Integer>();
				childBucket.foreignIndexEntries.put(foreignIndexName, existingForeignIndexEntries);
			}
			
			existingForeignIndexEntries.add(newForeignIndexEntry);
		} else {
			childBucket.addForeignIndexEntry(dataValues, foreignIndexName, newForeignIndexEntry);
		}
	}
	
	public void removeIndexEntry(List<String> dataValues) {
		String dataValue = getCorrespondingDataValue(dataValues);
		
		if(depth == 1) {
			childBuckets.remove(dataValue);
		} else {
			HashBucket childBucket = childBuckets.get(dataValue);
			childBucket.removeIndexEntry(dataValues);
			if(childBucket.size() == 0)
				childBuckets.remove(dataValue);
		}
	}
	
	public void removeForeignIndexEntry(List<String> dataValues, String foreignIndexName, Integer foreignIndexEntry) {
		String dataValue = getCorrespondingDataValue(dataValues);
		HashBucket childBucket = childBuckets.get(dataValue);
		
		if(depth == 1) {
			Set<Integer> entries = childBucket.foreignIndexEntries.get(foreignIndexName);
			entries.remove(foreignIndexEntry);
			if(entries.size() == 0)
				childBucket.foreignIndexEntries.remove(foreignIndexName);
		} else {
			childBucket.removeForeignIndexEntry(dataValues, foreignIndexName, foreignIndexEntry);
		}
	}
	
	public void removeForeignIndex(String foreignIndexName) {
		if(depth == 0) {
			foreignIndexEntries.remove(foreignIndexName);
		} else {
			for(String dataValue : childBuckets.keySet()) {
				HashBucket childBucket = childBuckets.get(dataValue);
				childBucket.removeForeignIndex(foreignIndexName);
			}
		}
	}
}
